﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace StableDiffusionTools.Progress
{
    public class UpdateTimer : IDisposable
    {
        Stopwatch stopwatch = new Stopwatch();
        TimeSpan lastUpdate = TimeSpan.Zero;
        object state = null;
        System.Threading.Timer timer;
        object syncLock = new object();
        private SynchronizationContext context = SynchronizationContext.Current;

        public event UpdateTimerEvent Update;
        protected virtual void OnUpdate(UpdateTimerEventArgs e)
        {
            if (Update != null)
            {
                if (context == null)
                    Update(this, e);
                else
                    context.Post(delegate { Update(this, e); }, null);
            }
        }

        private int updatePeriod;
        public int UpdatePeriod { get => updatePeriod; set => updatePeriod = value; }

        private int timerPeriod;
        public int TimerPeriod
        {
            get => timerPeriod;
            set
            {
                if (stopwatch.IsRunning)
                    timer.Change(timerPeriod, timerPeriod);

                timerPeriod = value;
            }
        }

        public bool Enabled
        {
            get => stopwatch.IsRunning;
            set
            {
                if (value && !stopwatch.IsRunning)
                {
                    stopwatch.Start();
                    timer.Change(timerPeriod, timerPeriod);
                }
                else if (!value && stopwatch.IsRunning)
                {
                    stopwatch.Stop();
                    timer.Change(Timeout.Infinite, timerPeriod);
                    lastUpdate = TimeSpan.Zero;
                }
            }
        }

        public UpdateTimer(int updatePeriod = 100, int timerPeriod = 50, bool startEnabled = true)
        {
            timer = new System.Threading.Timer(TimerTick, this, Timeout.Infinite, timerPeriod);

            this.updatePeriod = updatePeriod;
            this.timerPeriod = timerPeriod;

            Enabled = startEnabled;
        }

        #region IDisposable Support
        private bool disposedValue = false;

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    Enabled = false;
                    timer?.Dispose();
                }

                disposedValue = true;
            }
        }

        public void Dispose()
        {
            Dispose(true);
        }
        #endregion

        private static void TimerTick(object s)
        {
            UpdateTimer t = s as UpdateTimer;
            t?.DoUpdate();
        }

        public void PushState(object state)
        {
            lock (syncLock)
            {
                this.state = state;
            }
        }

        private void DoUpdate()
        {
            if (Enabled)
            {
                if (lastUpdate == TimeSpan.Zero)
                    lastUpdate = stopwatch.Elapsed;

                TimeSpan newUpdate = stopwatch.Elapsed;
                TimeSpan delta = newUpdate.Subtract(lastUpdate);
                if (delta.TotalMilliseconds >= updatePeriod)
                {
                    lastUpdate = newUpdate;
                    lock (syncLock)
                    {
                        if (state != null)
                        {
                            OnUpdate(new UpdateTimerEventArgs(delta, newUpdate, state));
                            state = null;
                        }
                    }
                }
            }
        }
    }
}
